
#include "EventQueue.h"
#include "query/QueryObject.h"
#include "gsl_ctx.h"

EventQueue::EventQueue()
{
    m_cs            = NULL;
    m_queueSize     = c_staticQueueSize;

    memset(m_queries,0,sizeof(m_queries));
    memset(m_flushed,0,sizeof(m_flushed));

    m_latestRetired = 0;
    m_headId   = m_queueSize - 1 ;
    m_tail = 0;
}

EventQueue::~EventQueue()
{
    for (unsigned int i = 0; i < c_staticQueueSize; i++)
    {
        assert(m_queries[i] == 0);
    }
}

bool
EventQueue::open(gsCtx* cs, gslQueryTarget target, EQManagerConfig config, uint32 engineMask)
{
    assert((config == EQManager_HIGH) || (config == EQManager_LOW));
    setSlotCount((int) config);
    assert((GpuEvent::InvalidID+1) % m_queueSize  == 0);

    m_cs = cs;
    
    m_headId   = m_queueSize - 1 ;
    m_tail = 0;
    m_latestRetired = 0;
    m_target = target;
    m_engineMask = engineMask;

    for (unsigned int i = 0; i < m_queueSize; i++)
    {
        m_queries[i] = cs->createQuery(target);
    }

    return true;
}

void
EventQueue::close()
{
    if (!m_cs) // the queue is unintialized.
    {
        return;
    }
    
    for (unsigned int i = 0; i < m_queueSize; i++)
    {
        m_cs->destroyQuery(m_queries[i]);
    }
    memset(m_queries, 0, sizeof(m_queries));
    memset(m_flushed, 0, sizeof(m_flushed));
    m_latestRetired = 0;
    m_headId   = m_queueSize - 1 ;
    m_tail = 0;
    m_cs = NULL;
}

void
EventQueue::begin()
{
    const CALuint slot = m_headId % m_queueSize;
    gslErrorCode ec =  m_queries[slot]->BeginQuery(m_cs, m_target, 0, m_engineMask);
    assert(ec == GSL_NO_ERROR);

    m_flushed[slot] = false;  // we've started a query, but it hasn't been checked yet...
}

uint32
EventQueue::end()
{
    uint32 ret = m_headId;
    const uint32 slot = m_headId % m_queueSize;

    m_queries[slot]->EndQuery(m_cs, 0);

    m_headId++;
    m_tail++;

    if (GpuEvent::InvalidID == m_headId)
    {
        // Flush on an event ID wrap around or when the Queue is going to wrap in
         flush();
         //roll numbers back to the beginning
         m_latestRetired = 0;
         m_headId = m_headId % m_queueSize;
         m_tail = m_tail % m_queueSize;
    }
    return ret;
}

bool
EventQueue::isDone(uint32 event)
{
    assert((event < GpuEvent::InvalidID) && "illegal event handle");
    // if the event is older the the last known retired event we
    // do not need to process it.
    if (event <= m_latestRetired)
    {
        return true;
    }

    // if the event is older than the oldest event handle we have
    //  we synchronize with the oldest event.
    if (event < m_tail)
    {
        return waitForEvent(m_tail, CAL_WAIT_LOW_CPU_UTILIZATION);
    }

    //
    //  If we've never called flush on the query object, go ahead flush the first time to ensure
    //  we never infinite loop
    //
    const uint32 slot = event % m_queueSize;
    if (!m_flushed[slot])
    {
        flush();
    }

    //
    // Since we're in between, we actually have to check to see if things are truely done
    //
    bool retVal = m_queries[slot]->IsResultAvailable(m_cs);

    // cache the most recently retired event
    if (retVal && (event < m_headId) && (event > m_latestRetired))
    {
        m_latestRetired = event;
    }

    return retVal;
}

bool
EventQueue::waitForEvent(uint32 event, uint32 waitType)
{
    // if we already retired a younger event we don't to process current events
    if (event <= m_latestRetired)
    {
        return true;
    }

    // if the event is older than the oldest event handle we have
    //  we synchronize with the oldest event
    if (event < m_tail)
    {
        event = m_tail;
    }

    //
    //  If we've never called flush on the query object, go ahead flush the first time to ensure
    //  we never infinite loop
    //
    const uint32 slot = event % m_queueSize;
    if (!m_flushed[slot])
    {
        flush();
    }
    uint64 param;
    m_queries[slot]->GetResult(m_cs, &param, (IOSyncWaitType) waitType);

    // cache the most recently retired event
    if ((event < m_headId) && (event > m_latestRetired))
    {
        m_latestRetired = event;
    }

    return (param != 0);
}

bool
EventQueue::flush()
{
    m_cs->Flush(false, m_engineMask);
    memset(m_flushed, 1, sizeof(m_flushed));
    return true;
}


void
EventQueue::setSlotCount(uint32 slotCount)
{
    if (slotCount < c_staticQueueSize)
    {
        m_queueSize = slotCount;
    }
    else
    {
        m_queueSize = c_staticQueueSize;
    }
}
